// SPDX-License-Identifier: GPL-2.0
#include "ocfs2.h"
#include "alloc.h"
#include "buffer_head_io.h"
#include "inode.h"
#include "journal.h"
#include "lockglue.h"
#include "namei.h"
#include "suballoc.h"
#include "sysfile.h"
#include "super.h"
#include "cluster/masklog.h"

#include "iso.h"

#include <linux/time.h>
#include <linux/rtc.h>

#define FMT_HOUR_DIFF 8
#define FMT_DAY_HOURS 24
#define FMT_YEAR_BEGIN 1900

int fmt_ctime(struct timespec64 te, char *st)
{
	char *p = st;
	struct rtc_time tm;

	rtc_time64_to_tm(te.tv_sec, &tm);
	/* Beijing time = UTC time + 8 hour */
	tm.tm_hour += FMT_HOUR_DIFF;
	if (tm.tm_hour >= FMT_DAY_HOURS) {
		tm.tm_hour -= FMT_DAY_HOURS;
		tm.tm_mday += 1;
	}
	tm.tm_year = tm.tm_year + FMT_YEAR_BEGIN;
	tm.tm_mon = tm.tm_mon + 1;

	p += sprintf(p, "%04d-%02d-%02d %02d:%02d:%02d",
			tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);

	return p - st;
}

/* refer to ocfs2_fillup_ro_iso_domain at ocfs2-tools */
static int ocfs2_fillup_ro_iso_domain(struct ocfs2_super *osb, u64 rid_blkno)
{
	int ret = 0;
	struct ocfs2_dinode *di;
	struct buffer_head *fe_bh = NULL;
	struct inode *rid_inode;
	handle_t *handle;

	rid_inode = ocfs2_iget(osb, rid_blkno, OCFS2_FI_FLAG_LOOKUP, 0);
	if (IS_ERR(rid_inode)) {
		ret = PTR_ERR(rid_inode);
		return ret;
	}

	inode_lock(rid_inode);
	ret = ocfs2_inode_lock(rid_inode, &fe_bh, 1);
	if (ret < 0) {
		mlog_errno(ret);
		goto out_mutex;
	}

	di = (struct ocfs2_dinode *) fe_bh->b_data;
	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		mlog_errno(ret);
		goto out_unlock;
	}

	ret = ocfs2_journal_access_di(handle, INODE_CACHE(rid_inode), fe_bh,
		OCFS2_JOURNAL_ACCESS_WRITE);
	if (ret) {
		mlog_errno(ret);
		goto out_commit;
	}

	// fillup data
	di->i_flags |= cpu_to_le32(OCFS2_RO_ISO_FL);
	di->id2.i_rodomain.ri_limit = cpu_to_le32(OCFS2_DEFAULT_RO_LIMIT);
	di->id2.i_rodomain.ri_total = cpu_to_le32(ocfs2_file_recs_per_inode(osb->sb));
	di->i_size = cpu_to_le64(ocfs2_file_recs_per_inode(osb->sb));

	ocfs2_update_inode_fsync_trans(handle, rid_inode, 1);
	ocfs2_journal_dirty(handle, fe_bh);

out_commit:
	ocfs2_commit_trans(osb, handle);

out_unlock:
	ocfs2_inode_unlock(rid_inode, 1);
	brelse(fe_bh);

out_mutex:
	inode_unlock(rid_inode);
	iput(rid_inode);

	return ret;
}

static int ocfs2_iso_rodomain_mknod(struct ocfs2_super *osb, u64 *ri_blkno)
{
	int ret;
	struct dentry *sys_root_dentry;
	struct dentry *rid_dentry;
	struct qstr dname;

	sys_root_dentry = d_make_root(osb->sys_root_inode);
	if (!sys_root_dentry) {
		mlog(ML_ERROR, "Failed to get sys dentry\n");
		return -ENOENT;
	}

	dname.name = ".ro_iso_domain";
	dname.len = strlen(".ro_iso_domain");
	dname.hash = full_name_hash(sys_root_dentry, dname.name, dname.len);
	rid_dentry = d_alloc(sys_root_dentry, &dname);
	if (!rid_dentry) {
		mlog(ML_ERROR, "Failed to create inode dentry\n");
		dput(sys_root_dentry);
		return -ENOMEM;
	}

	ret = ocfs2_mknod(osb->sys_root_inode, rid_dentry, S_IFREG | 0400, 0);
	if (ret) {
		mlog_errno(ret);
		goto out;
	}

	*ri_blkno = rid_dentry->d_inode->i_ino;
	mlog(ML_NOTICE, "%s: generate .ro_iso_domain success, blkno=%llu\n",
		osb->uuid_str, *ri_blkno);

out:
	dput(rid_dentry);
	return 0;
}

/* refer to create_ro_iso_domain_file at ocfs2-tools */
static int ocfs2_iso_rodomain_remkfs(struct ocfs2_super *osb, u64 *ri_blkno)
{
	int ret;
	struct ocfs2_dinode *super_di = NULL;

	ret = ocfs2_iso_rodomain_mknod(osb, ri_blkno);
	if (ret) {
		mlog_errno(ret);
		return ret;
	}

	// write .ro_iso_domain to disk
	ret = ocfs2_fillup_ro_iso_domain(osb, *ri_blkno);
	if (ret) {
		mlog(ML_ERROR, "Failed while adding .ro_iso_domain data\n");
		return ret;
	}

	// write super
	mutex_lock(&osb->system_file_mutex);
	ret = ocfs2_super_block_lock(osb, 1);
	if (ret < 0) {
		mutex_unlock(&osb->system_file_mutex);
		mlog_errno(ret);
		return ret;
	}
	super_di = (struct ocfs2_dinode *) osb->osb_super_bh->b_data;
	super_di->id2.i_super.rid_blkno = cpu_to_le64(*ri_blkno);
	ret = ocfs2_write_super_or_backup(osb, osb->osb_super_bh);
	if (ret < 0)
		mlog_errno(ret);
	ocfs2_super_block_unlock(osb, 1);
	mutex_unlock(&osb->system_file_mutex);

	spin_lock(&osb->osb_ro_isolation_lock);
	osb->osb_ro_isolation->rid_blkno = *ri_blkno;
	spin_unlock(&osb->osb_ro_isolation_lock);

	/* Here turn osb->osb_ro_iso_support to 1, Do we need to do this?  */
	spin_lock(&osb->osb_lock);
	osb->osb_ro_iso_support = 1;
	spin_unlock(&osb->osb_lock);

	return ret;
}

struct inode *ocfs2_get_rid_inode(struct ocfs2_super *osb, int flag)
{
	u64 rid_blkno;
	struct inode *ri_inode = NULL;
	struct ocfs2_dinode *super_di = NULL;
	int ret;

	spin_lock(&osb->osb_ro_isolation_lock);
	rid_blkno = osb->osb_ro_isolation->rid_blkno;
	spin_unlock(&osb->osb_ro_isolation_lock);
	if (rid_blkno == 0) {
		mlog(ML_ERROR, "%s: Get 0 while getting .ro_iso_domain inode!\n", osb->uuid_str);
		if (flag == 0)
			return NULL;
		// try to get rid_blkno from super_block on disk
		mutex_lock(&osb->system_file_mutex);
		ret = ocfs2_super_block_lock(osb, 1);
		if (ret < 0) {
			mutex_unlock(&osb->system_file_mutex);
			mlog_errno(ret);
			return NULL;
		}
		super_di = (struct ocfs2_dinode *) osb->osb_super_bh->b_data;
		rid_blkno = le64_to_cpu(super_di->id2.i_super.rid_blkno);
		ocfs2_super_block_unlock(osb, 1);
		mutex_unlock(&osb->system_file_mutex);
		if (rid_blkno == 0) {
			ret = ocfs2_iso_rodomain_remkfs(osb, &rid_blkno);
			if (ret)
				return NULL;
		} else {
			spin_lock(&osb->osb_ro_isolation_lock);
			osb->osb_ro_isolation->rid_blkno = rid_blkno;
			spin_unlock(&osb->osb_ro_isolation_lock);
		}
	}
	ri_inode = ocfs2_iget(osb, rid_blkno, OCFS2_FI_FLAG_LOOKUP, 0);
	if (IS_ERR(ri_inode)) {
		mlog(ML_ERROR, "%s: Could not get .ro_iso_domain inode!\n", osb->uuid_str);
		return NULL;
	}

	return ri_inode;
}

/*
 * After ocfs2_ro_iso_domain is read from the storage device,
 * this function must be called to verify it before using it.
 */
int validate_ro_iso_inode(struct ocfs2_super *osb, struct ocfs2_dinode *di)
{
	struct ocfs2_ro_iso_domain *rid = &(di->id2.i_rodomain);

	if (!(di->i_flags & cpu_to_le32(OCFS2_RO_ISO_FL))) {
		mlog(ML_ERROR, "Inode %llu is ro_iso_domain, but it's flag is ambiguous\n",
				(unsigned long long) le64_to_cpu(di->i_blkno));
		return -EROFS;
	}
	if (le32_to_cpu(rid->ri_total) == 0 ||
			le32_to_cpu(rid->ri_total) > ocfs2_file_recs_per_inode(osb->sb)) {
		mlog(ML_ERROR, "Inode ro_iso_domain has bad ri_total[%u]\n", rid->ri_total);
		return -EROFS;
	}
	if (rid->ri_limit > rid->ri_total) {
		mlog(ML_ERROR, "Inode ro_iso_domain has bad ri_limit[%u]\n", rid->ri_limit);
		return -EROFS;
	}
	if (rid->ri_next_index > rid->ri_total) {
		mlog(ML_ERROR, "Inode ro_iso_domain has bad ri_next_index[%u]\n",
				rid->ri_next_index);
		return -EROFS;
	}
	return 0;
}

int ocfs2_ro_files_output(char *buf, struct osb_ro_isolation *ori)
{
	struct ocfs2_ro_isolation_item *item;
	char st[OCFS2_PROC_TIME_LEN];
	char *p = buf;

	p += sprintf(p, "%10s|%44s|%20s|%4s|%6s\n", "inode", "func", "time", "node", "status");

	list_for_each_entry(item, &ori->ro_isolation_files, ro_list) {
		fmt_ctime(item->ro_time, st);
		p += sprintf(p, "%10llu|%44s|%20s|%4d|%6d\n", item->ro_blkno, item->ro_func, st,
				item->ro_node_num, item->ro_status);
	}

	return p - buf;
}

int check_inode_isolated(struct osb_ro_isolation *ori, u64 blkno)
{
	struct ocfs2_ro_isolation_item *item;

	list_for_each_entry(item, &ori->ro_isolation_files, ro_list) {
		if (item->ro_blkno == blkno)
			return 1;
	}

	return 0;
}

int ocfs2_check_inode_ro_on_super(struct ocfs2_super *osb, u64 blkno)
{
	int ret = 0;
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;

	ret = check_inode_isolated(ori, blkno);
	if (ret == 1) {
		mlog(ML_ERROR, "%s: Inode %llu has isolated on super.",
				osb->uuid_str, blkno);
	}
	return ret;
}

struct ocfs2_ro_iso_param *ocfs2_rip_build(int type, struct inode *inode,
		struct buffer_head *fe_bh, int *status)
{
	struct ocfs2_ro_iso_param *rip;

	rip = kzalloc(sizeof(struct ocfs2_ro_iso_param), GFP_KERNEL);
	if (!rip) {
		mlog(ML_ERROR, "Unable to alloc ro isolation param.\n");
		return NULL;
	}
	rip->ro_type = (enum ocfs2_rip_type) type;
	rip->inode = inode;
	rip->fe_bh = fe_bh;
	rip->status = status;
	if (inode)
		rip->blkno = le64_to_cpu(inode->i_ino);
	return rip;
}

struct ocfs2_ro_iso_param *ocfs2_rip_build_t1(struct inode *inode, int *status)
{
	return ocfs2_rip_build(OCFS2_RO_ISO_INODE_NO_BH, inode, NULL, status);
}

struct ocfs2_ro_iso_param *ocfs2_rip_build_t2(struct inode *inode,
		struct buffer_head *fe_bh, int *status)
{
	return ocfs2_rip_build(OCFS2_RO_ISO_INODE_WITH_BH, inode, fe_bh, status);
}

struct ocfs2_ro_iso_param *ocfs2_rip_build_t3(struct inode *inode, int *status)
{
	/* The param inode is used for synchronous check i_mode,
	 * cannot be used for asynchronous ocfs2_iso. */
	u64 blkno = inode->i_ino;
	struct ocfs2_ro_iso_param *rip = ocfs2_rip_by_blkno(blkno, status);

	mlog(ML_ERROR, "dinode %llu, i_mode:%x, is_dir:%x, is_reg:%x\n", blkno,
		inode->i_mode, S_ISDIR(inode->i_mode), S_ISREG(inode->i_mode));
	rip->ro_type = OCFS2_RO_ISO_FOR_ADL;
	return rip;
}

struct ocfs2_ro_iso_param *ocfs2_rip_by_blkno(u64 blkno, int *status)
{
	struct ocfs2_ro_iso_param *rip;

	rip = kzalloc(sizeof(struct ocfs2_ro_iso_param), GFP_KERNEL);
	if (!rip) {
		mlog(ML_ERROR, "Unable to alloc ro isolation param.\n");
		return NULL;
	}
	rip->ro_type = OCFS2_RO_ISO_INODE_BLKNO;
	rip->status = status;
	rip->blkno = blkno;
	return rip;
}

static inline void ocfs2_flush_ro_flag(struct ocfs2_dinode *di, u64 blkno)
{
	mlog(ML_NOTICE, "Start. inode %llu i_flags: 0x%x\n", blkno, le32_to_cpu(di->i_flags));
	di->i_flags |= cpu_to_le32(OCFS2_RO_ISOLATION_FL);
	mlog(ML_NOTICE, "Finish. inode %llu i_flags: 0x%x\n", blkno, le32_to_cpu(di->i_flags));
}

static int ocfs2_flush_inode_isolation_t1(struct ocfs2_super *osb, struct inode *inode)
{
	int ret = 0;
	struct ocfs2_dinode *di;
	struct buffer_head *fe_bh = NULL;
	handle_t *handle;

	if (!inode) {
		ret = -EINVAL;
		mlog_errno(ret);
		return ret;
	}

	ret = ocfs2_read_inode_block(inode, &fe_bh);
	if (ret) {
		mlog_errno(ret);
		goto out;
	}

	di = (struct ocfs2_dinode *) fe_bh->b_data;
	if ((di->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL))) {
		mlog(ML_NOTICE, "Skip system inode %llu", di->i_blkno);
		ret = -1;
		goto out;
	}

	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		mlog_errno(ret);
		goto out;
	}

	ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), fe_bh,
			OCFS2_JOURNAL_ACCESS_WRITE);
	if (ret) {
		mlog_errno(ret);
		goto out_commit;
	}

	ocfs2_flush_ro_flag(di, le64_to_cpu(inode->i_ino));

	ocfs2_update_inode_fsync_trans(handle, inode, 1);
	ocfs2_journal_dirty(handle, fe_bh);

out_commit:
	ocfs2_commit_trans(osb, handle);

out:
	brelse(fe_bh);
	return ret;
}

static int ocfs2_flush_inode_isolation_t2(struct ocfs2_super *osb,
		struct inode *inode, struct buffer_head *fe_bh)
{
	int ret = 0;
	struct ocfs2_dinode *di;
	handle_t *handle;

	di = (struct ocfs2_dinode *) fe_bh->b_data;
	if ((di->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL))) {
		mlog(ML_NOTICE, "Skip system inode %llu", di->i_blkno);
		return -1;
	}

	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		mlog_errno(ret);
		goto out;
	}

	ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), fe_bh,
			OCFS2_JOURNAL_ACCESS_WRITE);
	if (ret) {
		mlog_errno(ret);
		goto out_commit;
	}

	ocfs2_flush_ro_flag(di, le64_to_cpu(inode->i_ino));

	ocfs2_update_inode_fsync_trans(handle, inode, 1);
	ocfs2_journal_dirty(handle, fe_bh);

out_commit:
	ocfs2_commit_trans(osb, handle);

out:
	return ret;
}

static int ocfs2_flush_inode_isolation_t4(struct ocfs2_super *osb, u64 blkno)
{
	int ret = 0;
	struct inode *inode;
	struct ocfs2_dinode *di;
	struct buffer_head *fe_bh = NULL;
	handle_t *handle;

	inode = ocfs2_iget(osb, blkno, OCFS2_FI_FLAG_LOOKUP, 0);
	if (IS_ERR(inode)) {
		mlog(ML_ERROR, "Exec ocfs2_iget failed by blkno [%llu].\n", blkno);
		return PTR_ERR(inode);
	}
	inode_lock(inode);

	ret = ocfs2_inode_lock(inode, &fe_bh, 1);
	if (ret < 0) {
		mlog_errno(ret);
		goto out_mutex;
	}

	di = (struct ocfs2_dinode *) fe_bh->b_data;
	if ((di->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL))) {
		mlog(ML_NOTICE, "Skip system inode %llu", di->i_blkno);
		ret = -EINVAL;
		goto out_unlock;
	}

	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		mlog_errno(ret);
		goto out_unlock;
	}

	ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), fe_bh,
		OCFS2_JOURNAL_ACCESS_WRITE);
	if (ret) {
		mlog_errno(ret);
		goto out_commit;
	}

	ocfs2_flush_ro_flag(di, le64_to_cpu(inode->i_ino));
	ocfs2_update_inode_fsync_trans(handle, inode, 1);
	ocfs2_journal_dirty(handle, fe_bh);

out_commit:
	ocfs2_commit_trans(osb, handle);

out_unlock:
	ocfs2_inode_unlock(inode, 1);
	brelse(fe_bh);

out_mutex:
	inode_unlock(inode);
	iput(inode);
	return ret;
}

/* write OCFS2_RO_ISOLATION_FL to dinode->i_flags */
int ocfs2_flush_inode_isolation(struct ocfs2_super *osb, struct ocfs2_ro_iso_param *rip)
{
	int ret = 0;

	if (rip->ro_type == OCFS2_RO_ISO_INODE_NO_BH) {
		struct ocfs2_inode_info *oi = OCFS2_I(rip->inode);
		struct ocfs2_lock_res *lockres = &oi->ip_inode_lockres;

		print_disk_lockres(osb, lockres);
		ret = ocfs2_flush_inode_isolation_t1(osb, rip->inode);
	} else if (rip->ro_type == OCFS2_RO_ISO_INODE_WITH_BH) {
		struct ocfs2_inode_info *oi = OCFS2_I(rip->inode);
		struct ocfs2_lock_res *lockres = &oi->ip_inode_lockres;

		print_disk_lockres(osb, lockres);
		ret = ocfs2_flush_inode_isolation_t2(osb, rip->inode, rip->fe_bh);
	} else if (rip->ro_type == OCFS2_RO_ISO_FOR_ADL) {
		/* only isolate on ocfs2_super, used at adl and ocfs2_validate_inode_block */
	} else if (rip->ro_type == OCFS2_RO_ISO_INODE_BLKNO) {
		ret = ocfs2_flush_inode_isolation_t4(osb, rip->blkno);
	} else if (rip->ro_type == OCFS2_RO_ISO_TO_ERROR) {
		ocfs2_error(osb->sb, "degenerate to ocfs2_error");
		ret = -EROFS;
	}
	return ret;
}

/*
 * Before calling this function, you must call the function
 * validate_ro_iso_inode() to verify ocfs2_ro_iso_domain.
 *
 * result: 0: not existed
 *        -1: existed, then can call ocfs2_ri_rec_remove
 *        -2: existed but node_num not matched. not allowd call ocfs2_ri_rec_remove
 */
static int ocfs2_inode_in_iso(struct ocfs2_super *osb, u64 blkno, struct buffer_head *ri_bh)
{
	struct ocfs2_dinode *di = (struct ocfs2_dinode *) ri_bh->b_data;
	struct ocfs2_ro_iso_domain *rid = &(di->id2.i_rodomain);
	int i;
	int count = le32_to_cpu(rid->ri_next_index);

	for (i = 0; i < count; i++) {
		if (le64_to_cpu(rid->ri_recs[i].rr_blkno) == blkno) {
			mlog(ML_NOTICE, "Inode[%llu] is in ro_iso_domain.\n", blkno);
			if (rid->ri_recs[i].rr_node_num != cpu_to_le32(osb->node_num)) {
				mlog(ML_NOTICE, "Inode node_num [%u] not matched this node [%d].\n",
						rid->ri_recs[i].rr_node_num, osb->node_num);
				return -OCFS2_ISO_NODE_NOT_MATCHED;
			} else {
				return -1;
			}
		}
	}
	mlog(ML_NOTICE, "Inode [%llu] is not in ro_iso_domain.\n", (unsigned long long) blkno);
	return 0;
}

/*
 * Before calling this function, you must call the function
 * validate_ro_iso_inode() to verify ocfs2_ro_iso_domain.
 */
static int ocfs2_ri_rec_remove(struct inode *ri_inode, struct buffer_head *ri_bh, u64 blkno)
{
	int status;
	handle_t *handle;
	struct ocfs2_super *osb = OCFS2_SB(ri_inode->i_sb);
	struct ocfs2_dinode *di = (struct ocfs2_dinode *) ri_bh->b_data;
	struct ocfs2_ro_iso_domain *rid = &(di->id2.i_rodomain);
	int index = le32_to_cpu(rid->ri_next_index);
	int i, j;

	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		status = PTR_ERR(handle);
		mlog_errno(status);
		return status;
	}
	status = ocfs2_journal_access_di(handle, INODE_CACHE(ri_inode),
			ri_bh, OCFS2_JOURNAL_ACCESS_WRITE);
	if (status < 0) {
		mlog_errno(status);
		ocfs2_commit_trans(osb, handle);
		return status;
	}

	for (i = 0; i < index; i++) {
		if (rid->ri_recs[i].rr_blkno == cpu_to_le64(blkno))
			break;
	}
	if (i >= index) { /* not found */
		ocfs2_commit_trans(osb, handle);
		return -1;
	}
	for (j = i; j < index - 1; j++)
		rid->ri_recs[j] = rid->ri_recs[j + 1];
	memset(&rid->ri_recs[index - 1], 0, sizeof(struct ocfs2_ro_iso_rec));
	rid->ri_next_index = cpu_to_le32(index - 1);

	ocfs2_journal_dirty(handle, ri_bh);
	ocfs2_commit_trans(osb, handle);

	mlog(ML_NOTICE, "Remove ri_rec success. rec count = %d\n", index - 1);
	return status;
}

/*
 * Before calling this function, you must call the function
 * validate_ro_iso_inode() to verify ocfs2_ro_iso_domain.
 */
static int ocfs2_ri_rec_append(struct inode *ri_inode, struct buffer_head *ri_bh,
	u64 blkno, struct timespec64 *time, const char *func_name, int node_num)
{
	int status;
	handle_t *handle;
	struct ocfs2_super *osb = OCFS2_SB(ri_inode->i_sb);
	struct ocfs2_dinode *di = (struct ocfs2_dinode *) ri_bh->b_data;
	struct ocfs2_ro_iso_domain *rid = &(di->id2.i_rodomain);
	struct ocfs2_ro_iso_rec *ri_rec = NULL;
	int index = 0;
	int limit = 0;

	index = le32_to_cpu(rid->ri_next_index);
	limit = le32_to_cpu(rid->ri_limit);
	if (index > limit) {
		ocfs2_error(osb->sb, "Number of read-only files reaches the limit[%d].", limit);
		return -EROFS;
	}

	ri_rec = &rid->ri_recs[index];
	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		status = PTR_ERR(handle);
		mlog_errno(status);
		return status;
	}
	status = ocfs2_journal_access_di(handle, INODE_CACHE(ri_inode),
			ri_bh, OCFS2_JOURNAL_ACCESS_WRITE);
	if (status < 0) {
		mlog_errno(status);
		ocfs2_commit_trans(osb, handle);
		return status;
	}

	ri_rec->rr_blkno = cpu_to_le64(blkno);
	ri_rec->rr_time = cpu_to_le64(time->tv_sec);
	memset(ri_rec->rr_func, 0, OCFS2_RO_FUNC_MAX_LEN);
	memcpy(ri_rec->rr_func, func_name, strlen(func_name));
	ri_rec->rr_node_num = cpu_to_le32(node_num);
	rid->ri_next_index = cpu_to_le32(index + 1);

	ocfs2_journal_dirty(handle, ri_bh);
	ocfs2_commit_trans(osb, handle);

	mlog(ML_NOTICE, "Append ri_rec success. index = %d\n", index);
	return status;
}

int ocfs2_flush_sys_isolation_block(struct ocfs2_super *osb, u64 blkno, const char *func,
	struct timespec64 *time, enum ocfs2_flush_rid_type type)
{
	int status;
	struct inode *ri_inode = NULL;
	struct buffer_head *ri_bh = NULL;

	ri_inode = ocfs2_get_rid_inode(osb, 1);
	if (!ri_inode) {
		status = -EINVAL;
		goto out;
	}

	inode_lock(ri_inode);

	status = ocfs2_inode_lock(ri_inode, &ri_bh, 1);
	if (status < 0) {
		mlog_errno(status);
		goto out_mutex;
	}
	status = validate_ro_iso_inode(osb, (struct ocfs2_dinode *) ri_bh->b_data);
	if (status) {
		/* status == -EROFS */
		ocfs2_error(osb->sb, "Invalid dinode ro_iso_domain.");
		goto out_unlock;
	}

	status = ocfs2_inode_in_iso(osb, blkno, ri_bh);
	if (status == 0 && type == OCFS2_RO_ISO_REC_APPEND) {
		/* write_block */
		status = ocfs2_ri_rec_append(ri_inode, ri_bh, blkno, time, func, osb->node_num);
		if (status)
			mlog(ML_ERROR, "Append record failed. inode[%llu].\n",
				(unsigned long long) blkno);
		goto out_unlock;
	}
	if (status < 0 && type == OCFS2_RO_ISO_REC_REMOVE) {
		status = ocfs2_ri_rec_remove(ri_inode, ri_bh, blkno);
		if (status)
			mlog(ML_ERROR,
				"Remove record failed. inode[%llu].\n",
				(unsigned long long) blkno);
	}

out_unlock:
	brelse(ri_bh);
	ocfs2_inode_unlock(ri_inode, 1);

out_mutex:
	inode_unlock(ri_inode);
	iput(ri_inode);

out:
	mlog(ML_NOTICE, "End to flush, type[%d], status[%d].\n", type, status);
	return status;
}

void ocfs2_handle_ro_isolation(struct super_block *sb,
		struct ocfs2_ro_iso_param *rip, const char *func)
{
	struct ocfs2_super *osb = OCFS2_SB(sb);
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;
	struct ocfs2_ro_isolation_item *item;
	u64 blkno = rip->blkno;
	int ret = 0;

	ret = check_inode_isolated(ori, blkno); /* deduplicate */
	if (ret) {
		mlog(ML_ERROR, "Inode %llu is isolated before.\n", blkno);
		return;
	}

	item = kmalloc(sizeof(struct ocfs2_ro_isolation_item), GFP_NOFS);
	if (!item) {
		mlog(ML_ERROR, "Unable to alloc osb_ro_isolation.\n");
		mlog_errno(-ENOMEM);
		return;
	}

	/* set to ro isolation domain. you can show it at [ /proc/fs/ocfs2/dm-xx/ro_files ] */
	mlog(ML_NOTICE, "%s: Start to isolate.\n", osb->uuid_str);
	INIT_LIST_HEAD(&item->ro_list);
	item->ro_blkno = blkno;
	ktime_get_real_ts64(&item->ro_time);
	strncpy(item->ro_func, func, OCFS2_RO_FUNC_MAX_LEN);
	item->ro_status = false;
	item->ro_rip = rip;
	item->ro_node_num = osb->node_num;

	spin_lock(&osb->osb_ro_isolation_lock);
	list_add_tail(&item->ro_list, &ori->ro_isolation_files);
	ori->ro_isolation_count = ori->ro_isolation_count + 1;
	spin_unlock(&osb->osb_ro_isolation_lock);

	queue_delayed_work(osb->ocfs2_wq, &osb->osb_ro_isolation_wq, 0);
}

static int ocfs2_do_ro_isolation(struct ocfs2_super *osb)
{
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;
	struct ocfs2_ro_isolation_item *item;
	int ret;

	item = list_entry(ori->ro_isolation_files.prev,
	struct ocfs2_ro_isolation_item, ro_list);

	mlog(ML_NOTICE, "%s: [%llu] isolate start.\n", osb->uuid_str, item->ro_blkno);

	if (item->ro_status) {
		mlog(ML_ERROR, "Skip when item->ro_status is true.\n");
		return -1;
	}

	/* write OCFS2_RO_ISOLATION_FL to dinode->i_flags */
	ret = ocfs2_flush_inode_isolation(osb, item->ro_rip);
	if (ret) {
		mlog(ML_ERROR, "Failed when flush inode.\n");
		return ret;
	}

	/* write record to ro_iso_domain */
	ret = ocfs2_flush_sys_isolation_block(osb,
			item->ro_blkno, item->ro_func, &item->ro_time, OCFS2_RO_ISO_REC_APPEND);
	if (ret) {
		mlog(ML_ERROR, "Failed when flush sys inode.\n");
		return ret;
	}

	item->ro_status = true;
	mlog(ML_NOTICE, "%s: [%llu] isolate success.\n", osb->uuid_str, item->ro_blkno);

	return 0;
}

void ocfs2_ro_isolation_worker(struct work_struct *work)
{
	int status;
	struct ocfs2_super *osb =
		container_of(work, struct ocfs2_super, osb_ro_isolation_wq.work);

	status = ocfs2_do_ro_isolation(osb);
	if (status < 0)
		mlog_errno(status);
	else
		ocfs2_init_steal_slots(osb);
}

/* remove OCFS2_RO_ISOLATION_FL at dinode->i_flags */
static int ocfs2_reset_inode_isolation(struct ocfs2_super *osb, struct inode *inode)
{
	int ret = 0;
	struct ocfs2_dinode *di;
	struct buffer_head *fe_bh = NULL;
	u64 blkno = le64_to_cpu(inode->i_ino);
	handle_t *handle;

	if (!inode) {
		ret = -EINVAL;
		mlog_errno(ret);
		return ret;
	}

	ret = ocfs2_read_inode_block_full(inode, &fe_bh, OCFS2_FI_FLAG_SKIP_FLAG);
	if (ret) {
		mlog_errno(ret);
		goto out;
	}

	di = (struct ocfs2_dinode *) fe_bh->b_data;
	if (!(di->i_flags & cpu_to_le32(OCFS2_RO_ISOLATION_FL))) {
		mlog(ML_ERROR, "Inode has no ro flags. %llu i_flags 0x%x\n",
				blkno, le32_to_cpu(di->i_flags));
		goto out;
	}

	handle = ocfs2_start_trans(osb, OCFS2_INODE_UPDATE_CREDITS);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		mlog_errno(ret);
		goto out;
	}

	ret = ocfs2_journal_access_di(handle, INODE_CACHE(inode), fe_bh,
			OCFS2_JOURNAL_ACCESS_WRITE);
	if (ret) {
		mlog_errno(ret);
		goto out_commit;
	}

	mlog(ML_NOTICE, "Start. inode %llu i_flags: 0x%x\n",
			blkno, le32_to_cpu(di->i_flags));
	di->i_flags &= ~cpu_to_le32(OCFS2_RO_ISOLATION_FL);
	mlog(ML_NOTICE, "Finish. inode %llu i_flags: 0x%x\n",
			blkno, le32_to_cpu(di->i_flags));

	ocfs2_update_inode_fsync_trans(handle, inode, 1);
	ocfs2_journal_dirty(handle, fe_bh);

out_commit:
	ocfs2_commit_trans(osb, handle);

out:
	brelse(fe_bh);
	return ret;
}

static int ocfs2_check_inode_in_rodomain(struct ocfs2_super *osb, u64 blkno)
{
	/* exist return -1; exist but node_num not match return -2; not exist return 0. */
	return ocfs2_flush_sys_isolation_block(osb, blkno, NULL, NULL, OCFS2_RO_ISO_SYS_CHECK);
}

/* remove inode from ro_iso_domain */
int ocfs2_reset_ro_isolation(struct ocfs2_super *osb, u64 blkno)
{
	int ret = 0;
	struct inode *inode;
	struct ocfs2_ro_isolation_item *item, *tmp;
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;

	if (ocfs2_recover_ro(osb)) {
		mlog(ML_ERROR, "Ocfs2 is readonly, operation aborted.\n");
		return -EINVAL;
	}

	ret = ocfs2_check_inode_in_rodomain(osb, blkno);
	/*
	 * By default, only the data isolated by its own node can be deleted.
	 * However, if ro_switch is turned off,
	 * you can forcibly reset the inode isolated by other nodes.
	 */
	if (ret == 0) { // Inode [%llu] is not in ro_iso_domain.
		return -EINVAL;
	} else if (ret == -OCFS2_ISO_NODE_NOT_MATCHED) { // Inode node_num [%u] not matched
		spin_lock(&osb->osb_lock);
		if (osb->osb_ro_iso_support != 0) {
			spin_unlock(&osb->osb_lock);
			return -EINVAL;
		}
		spin_unlock(&osb->osb_lock);
		mlog(ML_NOTICE,
			"Inode [%llu] is isolated by other nodes, now forcibly reset it.\n", blkno);
	}

	inode = ocfs2_iget(osb, blkno, OCFS2_FI_FLAG_SKIP_FLAG, 0);
	if (IS_ERR(inode)) {
		ret = PTR_ERR(inode);
		mlog(ML_ERROR, "Exec ocfs2_iget failed by blkno [%llu].\n", blkno);
		return ret;
	}

	mlog(ML_NOTICE, "Start reset isolation flag on inode %llu\n", blkno);

	/* remove OCFS2_RO_ISOLATION_FL at dinode->i_flags */
	inode_lock(inode);
	ret = ocfs2_reset_inode_isolation(osb, inode);
	inode_unlock(inode);
	iput(inode);
	if (ret)
		return ret;

	/* remove from osb->osb_ro_isolation */
	spin_lock(&osb->osb_ro_isolation_lock);
	list_for_each_entry_safe(item, tmp, &ori->ro_isolation_files, ro_list) {
		if (item->ro_blkno == blkno) {
			list_del_init(&item->ro_list);
			kfree(item);
			break;
		}
	}
	ori->ro_isolation_count = ori->ro_isolation_count <= 1 ? 0 : (ori->ro_isolation_count - 1);
	spin_unlock(&osb->osb_ro_isolation_lock);

	/* remove from ro_iso_domain block */
	ret = ocfs2_flush_sys_isolation_block(osb, blkno, NULL, NULL, OCFS2_RO_ISO_REC_REMOVE);

	return ret;
}

static void ocfs2_clean_ro_isolation_files(struct ocfs2_super *osb)
{
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;
	struct ocfs2_ro_isolation_item *item, *tmp;

	if (list_empty(&ori->ro_isolation_files))
		return;

	/* remove all of osb->osb_ro_isolation */
	spin_lock(&osb->osb_ro_isolation_lock);
	list_for_each_entry_safe(item, tmp, &ori->ro_isolation_files, ro_list) {
		list_del_init(&item->ro_list);
		kfree(item);
	}
	ori->ro_isolation_count = 0;
	spin_unlock(&osb->osb_ro_isolation_lock);
}

int ocfs2_recovery_rodomain_item(struct ocfs2_super *osb, struct ocfs2_ro_iso_domain *rid, int i)
{
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;
	struct ocfs2_ro_isolation_item *item;
	struct timespec64 ts;

	item = kmalloc(sizeof(struct ocfs2_ro_isolation_item), GFP_NOFS);
	if (!item) {
		mlog(ML_ERROR, "Cannot alloc item at %llu.\n",
				(unsigned long long) le32_to_cpu(rid->ri_recs[i].rr_blkno));
		return -ENOMEM;
	}
	item->ro_blkno = le32_to_cpu(rid->ri_recs[i].rr_blkno);
	item->ro_node_num = le32_to_cpu(rid->ri_recs[i].rr_node_num);
	ts.tv_sec = le64_to_cpu(rid->ri_recs[i].rr_time); /* ts.tv_nsec = 0; */
	item->ro_time = ts;
	strncpy(item->ro_func, rid->ri_recs[i].rr_func, OCFS2_RO_FUNC_MAX_LEN);
	item->ro_status = true;
	INIT_LIST_HEAD(&item->ro_list);
	spin_lock(&osb->osb_ro_isolation_lock);
	list_add_tail(&item->ro_list, &ori->ro_isolation_files);
	spin_unlock(&osb->osb_ro_isolation_lock);
	mlog(ML_NOTICE,
		"%s: Isolated dinode [%llu] recovery success.\n", osb->uuid_str, item->ro_blkno);
	return 0;
}

int ocfs2_recovery_rodomain(struct ocfs2_super *osb)
{
	int ret, count, i;
	struct inode *ri_inode = NULL;
	struct buffer_head *ri_bh = NULL;
	struct ocfs2_dinode *di = NULL;
	struct ocfs2_ro_iso_domain *rid = NULL;
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;

	ri_inode = ocfs2_get_rid_inode(osb, 1);
	if (!ri_inode)
		return -EINVAL;

	inode_lock(ri_inode);
	ret = ocfs2_inode_lock(ri_inode, &ri_bh, 0);
	if (ret < 0) {
		mlog_errno(ret);
		goto out;
	}

	di = (struct ocfs2_dinode *) ri_bh->b_data;
	ret = validate_ro_iso_inode(osb, di);
	if (ret) {
		/* ret == -EROFS */
		ocfs2_error(osb->sb, "Invalid dinode ro_iso_domain.");
		goto out_mutex;
	}

	rid = &(di->id2.i_rodomain);
	count = le32_to_cpu(rid->ri_next_index);

	ocfs2_clean_ro_isolation_files(osb);
	for (i = 0; i < count; i++) {
		ret = ocfs2_recovery_rodomain_item(osb, rid, i);
		if (ret)
			goto out_mutex;
	}
	ori->ro_isolation_count = count;
	ori->ro_isolation_limit = le32_to_cpu(rid->ri_limit);

out_mutex:
	brelse(ri_bh);
	ocfs2_inode_unlock(ri_inode, 0);

out:
	inode_unlock(ri_inode);
	iput(ri_inode);

	return ret;
}

void ocfs2_ro_recovery_worker(struct work_struct *work)
{
	int status;
	struct ocfs2_super *osb =
		container_of(work, struct ocfs2_super, osb_ro_recovery_wq.work);

	status = ocfs2_recovery_rodomain(osb);
	if (status < 0)
		mlog_errno(status);
	else
		ocfs2_init_steal_slots(osb);
}

static int ocfs2_update_ro_limit(struct inode *ri_inode,
		struct buffer_head *ri_bh, unsigned int limit)
{
	int status;
	handle_t *handle;
	struct ocfs2_super *osb = OCFS2_SB(ri_inode->i_sb);
	struct osb_ro_isolation *ori = osb->osb_ro_isolation;
	struct ocfs2_dinode *di = (struct ocfs2_dinode *) ri_bh->b_data;
	struct ocfs2_ro_iso_domain *rid = &(di->id2.i_rodomain);
	unsigned int total = 0;

	total = le32_to_cpu(rid->ri_total);
	if (limit > total) {
		status = -EINVAL;
		mlog(ML_ERROR, "%s: The limit value %u cannot exceed the total %u!\n",
			osb->uuid_str, limit, total);
		return status;
	}

	handle = ocfs2_start_trans(osb, OCFS2_RO_ISO_SYS_UPDATE);
	if (IS_ERR(handle)) {
		status = PTR_ERR(handle);
		mlog_errno(status);
		return status;
	}
	status = ocfs2_journal_access_di(handle, INODE_CACHE(ri_inode),
			ri_bh, OCFS2_JOURNAL_ACCESS_WRITE);
	if (status < 0) {
		mlog_errno(status);
		ocfs2_commit_trans(osb, handle);
		return status;
	}

	rid->ri_limit = cpu_to_le32(limit);

	spin_lock(&osb->osb_ro_isolation_lock);
	ori->ro_isolation_limit = limit;
	spin_unlock(&osb->osb_ro_isolation_lock);

	ocfs2_journal_dirty(handle, ri_bh);
	ocfs2_commit_trans(osb, handle);

	return status;
}

int ocfs2_write_rolimit_to_disk(struct ocfs2_super *osb, unsigned int limit)
{
	int status;
	struct inode *ri_inode = NULL;
	struct buffer_head *ri_bh = NULL;

	ri_inode = ocfs2_get_rid_inode(osb, 0);
	if (!ri_inode) {
		status = -ENOENT;
		goto out;
	}

	inode_lock(ri_inode);

	status = ocfs2_inode_lock(ri_inode, &ri_bh, 1);
	if (status < 0) {
		mlog_errno(status);
		goto out_unlock;
	}

	status = validate_ro_iso_inode(osb, (struct ocfs2_dinode *) ri_bh->b_data);
	if (status) {
		/* status == -EROFS */
		ocfs2_error(osb->sb, "Invalid dinode ro_iso_domain.");
		goto out_mutex;
	}

	/* update ro_limit */
	status = ocfs2_update_ro_limit(ri_inode, ri_bh, limit);
	if (status) {
		mlog(ML_ERROR, "%s: Set limit to ocfs2_ro_iso_domain failed. limit[%u].\n",
				osb->uuid_str, limit);
	}

out_mutex:
	brelse(ri_bh);
	ocfs2_inode_unlock(ri_inode, 1);

out_unlock:
	inode_unlock(ri_inode);
	iput(ri_inode);

out:
	mlog(ML_NOTICE, "End to write_rolimit_to_disk, limit[%d].\n", limit);
	return status;
}

int ocfs2_read_rolimit_from_disk(struct ocfs2_super *osb, unsigned int *limit)
{
	int status;
	struct inode *ri_inode = NULL;
	struct buffer_head *ri_bh = NULL;
	struct ocfs2_dinode *di = NULL;

	ri_inode = ocfs2_get_rid_inode(osb, 0);
	if (!ri_inode)
		return -EINVAL;

	inode_lock(ri_inode);

	status = ocfs2_inode_lock(ri_inode, &ri_bh, 0);
	if (status < 0) {
		mlog_errno(status);
		goto out_unlock;
	}
	di = (struct ocfs2_dinode *) ri_bh->b_data;
	status = validate_ro_iso_inode(osb, di);
	if (status) {
		/* status == -EROFS */
		ocfs2_error(osb->sb, "Invalid dinode ro_iso_domain.");
	} else {
		*limit = le32_to_cpu(di->id2.i_rodomain.ri_limit);
	}

	brelse(ri_bh);
	ocfs2_inode_unlock(ri_inode, 0);

out_unlock:
	inode_unlock(ri_inode);
	iput(ri_inode);

	return status;
}

int ocfs2_check_inode_ro_flag(struct ocfs2_super *osb, struct buffer_head *di_bh)
{
	/* use at ocfs2_file_write_iter,ocfs2_validate_inode_block */
	int ret = 0;
	struct ocfs2_dinode *fe = NULL;

	if (!osb->osb_ro_iso_support)
		return 0;

	fe = (struct ocfs2_dinode *) di_bh->b_data;
	if ((fe->i_flags & cpu_to_le32(OCFS2_SYSTEM_FL)))
		return 0;

	if ((fe->i_flags & cpu_to_le32(OCFS2_RO_ISOLATION_FL))) {
		ret = 1;
		mlog(ML_ERROR, "%s: Inode %llu has isolated.", osb->uuid_str, fe->i_blkno);
	}

	return ret;
}

int ocfs2_inode_lock_and_check_ro_flag(struct inode *inode)
{
	/* use at ocfs2_file_read_iter,ocfs2_ioctl */
	int ret = 0;
	struct buffer_head *bh = NULL;
	struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);

	if (!osb->osb_ro_iso_support)
		return 0;

	ret = ocfs2_inode_lock(inode, &bh, 0);
	if (ret < 0) {
		mlog_errno(ret);
		return ret;
	}

	if (bh == NULL) {
		ret = -1;
		mlog_errno(-EINVAL);
		goto out;
	}

	if (ocfs2_check_inode_ro_flag(osb, bh) == 1)
		ret = -1;

out:
	ocfs2_inode_unlock(inode, 0);
	brelse(bh);
	return ret;
}
